﻿/**
 * nenglong.system.js 
 * Library v1.0.0
 *
 * Copyright (c) 2003 - 2018, nenglong
 * All rights reserved.
 *
 * Date: 2013-06-30
 * 需引用：jquer, labjs
 */
(function (global) {
    /**
    * 命名空间
    */
    var Namespace = {
        //注册命名空间，返回命名空间对象
        Register: function (fullNS) {
            if (!Namespace.Exists(fullNS)) {
                var nsArray = fullNS.split('.');
                var sEval = "";
                var sNS = "";
                for (var i = 0; i < nsArray.length; i++) {
                    if (i != 0) sNS += ".";
                    sNS += nsArray[i];
                    sEval += "if (typeof(" + sNS + ") == 'undefined') " + sNS + " = new Object();";
                }
                if (sEval != "") eval(sEval);
            }
            return typeof eval(fullNS)=="function" ? new eval(fullNS) : eval(fullNS);
        },
        //检查命名空间是否存在
        Exists: function (fullNS) {
            var nsArray = fullNS.split('.');
            var sNS = "";
            for (var i = 0; i < nsArray.length; i++) {
                if (i != 0) sNS += ".";
                sNS += nsArray[i];
                if (eval("typeof(" + sNS + ") == 'undefined'")) {
                    return false;
                }
            }
            return true;
        }
    };
    //用于$NL.log.i中的日志存放，用于过滤筛选
    var logMessage = [];
    /**
    * 接口，统一用$NL做为入口
    */
    global.$NL = {
        /**
        *全局配置
        */
        config: {
            urlRoot: "",
            isDebug: false,
            getJsComboUrl: function(){
                return $NL.config.urlRoot + "JsCombo1.ashx";
            },
            getScriptUrl: function (url) {
                if ($NL.config.isDebug) {
                    return $NL.config.urlRoot + url;
                }
                else {
                    return $NL.config.urlRoot + "cachebuffer/Cache_" + url;
                }
            },
            getScriptTagFile: function (tagFile) {
                return $NL.config.urlRoot + "cachebuffer/" + tagFile;
            }
        },
        /**
        *日志记录
        */
        log: {
            //一般信息日志，可发布于正式版本，但只有isDebug状态下才输出调试信息
            i: function (mess) {
                try{
                    if ($NL.config.isDebug) {
                        console.log(mess);
                        logMessage.push(mess);
                    }
                    else {
                    }
                }
                catch (err) {
                }
            },
            //警告日志
            w: function(mess){
                try {
                    console.warn(mess);
                    console.warn($NL.log.w.caller.arguments);
                }
                catch (err) {
                }
            },
            //错误日志
            e: function (mess) {
                try {
                    mess = $.makeArray(mess);
                    $.each(mess, function (n, v) {
                        console.error(v);
                    });
                    console.error($NL.log.e.caller.arguments);
                    console.error($NL.log.e.caller);
                    console.error($NL.log.e.caller.caller);
                    console.error($NL.log.e.caller.caller.caller);
                }
                catch (err) {
                }
            },
            //debug时用于输出调试信息，调试完后要删除相关的代码，不能发布于正式版本
            d: function (mess) {
                try {
                    console.debug("debug信息:" + mess);
                    console.debug($NL.log.d.caller.arguments);
                    console.debug("调用函数：" + $NL.log.d.caller);
                }
                catch (err) {
                }
            },
            //筛选信息
            f: function (mess) {
                return $.grep(logMessage, function (n,i) {
                    return n.indexOf(mess) >= 0;
                }).join("\n");
            }
        },
        /**
        *脚本加载器，内部加载器使用$LAB进行加载
        */
        script: new (function () {
            /*== 私有变量 ==*/
            ///加载器列表
            var loaderList = [];
            ///已加载文件列表
            var loadedFiles = [];
            ///文件地图
            var jsmap = [];
            ///引用关系记录
            var moduleReferenceCache = {};
            /*== 私有函数 ==*/
            ///获取命名空间，获取脚本的路径，即jsmapbase 及 jsmap文件中配置的路径
            function getScriptSrcUrlByNamespace(ns) {
                for (var i = 0; i < jsmap.length; i++) {
                    for (var j = 0; j < jsmap[i].src.length; j++) {
                        if (jsmap[i].src[j].namespace.toLowerCase() == ns.toLowerCase()) {
                            return jsmap[i].src[j].url;
                        }
                    }
                }
                return null;
            }
            ///通过请求的路径，生成真正的访问路么，主要是拼接前缀，及通过isDebug参数的设置判断是否要转为压缩文件的路么
            function getScriptTrueUrl(url) {
                if (url.indexOf("~") >= 0) return { other: true, url: url.replace("~", $NL.config.urlRoot) };
                for (var i = 0; i < jsmap.length; i++) {
                    for (var j = 0; j < jsmap[i].src.length; j++) {

                        if (jsmap[i].src[j].url.toLowerCase() == url.toLowerCase()) {
                            if ($NL.config.isDebug) {
                                return { other: false, url: $NL.config.getScriptUrl(url) };
                            }
                            else {
                                return { other: false, url: $NL.config.getScriptUrl(jsmap[i].compress) };
                            }
                        }
                    }
                }
                $NL.log.i($NL.config.urlRoot + url + " is not in jsmap");
                return { other: true, url: $NL.config.urlRoot + url };
            };
            /*== 公有函数 ==*/
            ///push脚本地图，提供给外界
            this.pushMap = function (maps) {
                $.merge(jsmap, maps);
            }
            //用于调试
            this.showJsMap = function () {
                return jsmap;
            }
            //用于调试
            this.showLoaderList = function () {
                return loaderList;
            }
            ///通过key取脚本地图中的元素
            this.getMapByKey = function (key) {
                for (var i = 0; i < jsmap.length; i++) {
                    if (jsmap[i].key == key) return jsmap[i];
                }
                return null;
            }
            ///单个脚本文件加载
            this.loadScript = function (url) {
                //$.loadScript(url1).loadScript(url2)中的链式操作，第一个loadScript是调用本函数，生成一个内部加载器对象loader，附加到内部变量中，并返回，后面链式操作的loadScript调用的是加载器的loadScript
                var l = new loader(url);
                loaderList.push(l);
                return l;
            };
            ///批量加载（内部转为loadScript的链式操作处理,第三个参数是命名空间，用于$NL.defined中的引用加载
            this.loadScripts = function (urls, fun, ns) {
                if (!$.isArray(urls) || urls.length == 0) {
                    fun();
                    return;
                }
                //如果是函数模块定义的引用加载，$NL.defined中调用，则记录检查相互引用的问题。
                if (typeof (ns) == "string") {
                    var loadFile = getScriptSrcUrlByNamespace(ns);
                    
                    $.each(urls, function (n, v) {
                        if (!!moduleReferenceCache[v] && $.inArray(loadFile, moduleReferenceCache[v]) >= 0) {
                            $NL.log.e(["存在相互引用错误：",
                                loadFile + "引用了" + urls,
                                v + "引用了" + moduleReferenceCache[v]]
                                );
                        }
                    });
                    moduleReferenceCache[loadFile] = urls;
                }

                var t = this.loadScript(urls[0]);
                for (var i = 1; i < urls.length; i++) {
                    t = t.loadScript(urls[i]);
                }
                t.wait(fun);
            };
            //加载模块
            this.loadModule = function (moduleKey, fun) {
                var map = this.getMapByKey(moduleKey);
                if (map != null) {
                    var files = [];
                    for (var j = 0; j < map.src.length; j++) {
                        files.push(map.src[j].url);
                    }
                    this.loadScripts(files, fun);
                }
            };
            this.existTagFile = function (tagFile, success, error) {
                //访问压缩标志文件（一个空文件），成功即表示服务器已压缩过，失败，则请求服务器执行脚本压缩
                var options = {
                    dataType: "script",
                    cache: false,
                    url: $NL.config.getScriptTagFile(tagFile),
                    success: success,
                    error: error
                };
                return jQuery.ajax(options);
            }
            ///压缩脚本
            this.comboScript = function (tagFile, fun, error) {
                error = error || function () { };
                //按jsmapbase.js jsmap.js中配置的对应关系，请求服务器压缩脚本
                var ops = {
                    "tagFile": tagFile
                }
                for (var i = 0; i < jsmap.length; i++) {
                    ops["compress" + i] = jsmap[i].compress;
                    var urls = [];
                    for (var j = 0; j < jsmap[i].src.length; j++) {
                        urls.push(jsmap[i].src[j].url);
                    };
                    ops["jsurls" + i] = urls.join(',');
                }
                var options = {
                    type: "post",
                    dataType: "script",
                    cache: false,
                    url: $NL.config.getJsComboUrl(),
                    success: fun,
                    error:error,
                    data: ops
                };
                return jQuery.ajax(options);

            };
            /// 命名空间加载完成呼叫的函数(供defined中调用)
            this.namespaceLoaded = function (ns) {
                var url = getScriptSrcUrlByNamespace(ns);
                if (url == null) {
                    $NL.log.i("找不到相应的加载文件，命名空间：" + ns);
                    return;
                }
                else {
                    //将加载成功的文件名添加到已加载文件列表中，需放在执行加载器的process之前
                    loadedFiles.push(url);
                    $NL.log.i("文件加载完成：" + url);
                    //遍历当前的所有加载器,设置加载器中的当前加载文件的状态,对设置成功的加载器，执行加载处理方法
                    for (var i = 0; i < loaderList.length; i++) {
                        if (loaderList[i].setFileLoaded(url)) {
                            //process中又会加载文件，加载完后又会调用这个方法，形成递归
                            loaderList[i].process();
                        }
                    }
                }
                //process中可能形成递归，采用模拟线程的方法，避免递归子函数中将loaderList后面的删除了，导致父函数出错
                setTimeout(function () {
                    //遍历当前的所有加载器，清理执行完的加载器
                    for (var i = loaderList.length - 1; i >= 0; i--) {
                        if (loaderList[i].files.length == 0) {
                            loaderList.splice(i, 1);
                        }
                    }
                }, 0);
            }
            /*== 内部类 ==*/
            ///加载器对象
            function loader(url) {
                this.files = []; //当前加载文件列表
                this.$lab = $LAB;//当前加载执行链，对应$LAB库的加载器
                this.loadScript(url);
                setTimeout($.proxy(this.process, this), 0);
            }
            ///将文件添加到当前加载列表
            loader.prototype.loadScript = function (url) {
                $NL.log.i("准备加载：" + url);
                if ($.inArray(url, loadedFiles) > -1) {
                    $NL.log.i("文件加载完成：" + url);
                }
                else {
                    this.files.push({ state: 0, url: url });
                }
                return this;
            }
            ///将函数添加到当前加载列表（即前面的文件加载完成后要执行的函数
            loader.prototype.wait = function (fun) {
                if (typeof (fun) == "undefined") fun = new Function();
                this.files.push(fun);
                return this;
            }
            ///遍历当前加载文件列表，设置文件加载状态（0--等待加载，1--加载中，2--完成加载)
            loader.prototype.setFileLoaded = function (url) {
                var fileLoaded = false;
                for (var i = 0; i < this.files.length; i++) {
                    if ($.isPlainObject(this.files[i]) && this.files[i].url.toLowerCase() == url.toLowerCase()) {
                        this.files[i].state = 2;
                        fileLoaded = true;
                    }
                }
                return fileLoaded;
            }
            ///实际加载函数，加载器的核心，包括文件加载及保证加载顺序
            loader.prototype.process = function () {
                //如果当前加载文件中最前面的是函数，则取出函数并执行，直到队列第一个不是函数的，如果是已加载完成，则删除，直到碰到没有加载完成的
                while (this.files.length > 0) {
                    if ($.isFunction(this.files[0])) {
                        this.$lab = this.$lab.wait(this.files.shift());
                    }
                    else if (this.files[0].state == 2) {
                        this.files.shift();
                    }
                    else {
                        break;
                    }
                }
                //var reload = false;
                var otherFiles = [];
                for (var i = 0; i < this.files.length; i++) {
                    //遍历队列，obj,obj,obj,fun,obj,obj,obj,fun，则加载前面三个obj，直到碰到fun,fun要等前面三个加载完才有执行
                    if ($.isPlainObject(this.files[i])) {
                        //如果对象是等待加载，则进行加载
                        if (this.files[i].state == 0) {
                            var u = getScriptTrueUrl(this.files[i].url);
                            this.$lab = this.$lab.script(u.url);
                            this.files[i].state = 1;
                            $NL.log.i("文件加载中：" + u.url);
                            if (u.other) otherFiles.push(this.files[i]);

                        }
                    }
                    else {
                        break;
                    }
                }
                //如果本次加载的文件中有第三方（或jsmap中没有定义的）的文件，则在$LAB加载链中添加wait方法，等这批文件加载完后执行
                if (otherFiles.length > 0) {
                    var self = this;
                    this.$lab.wait(function () {
                        //对于第三方的文件加载完即修改状态为2（完成），并将文件放入已加载列表中，对于不是第三方的，要等到NLdefined真正执行了才设为加载完成
                        for (var i = 0; i < otherFiles.length; i++) {
                            otherFiles[i].state = 2;
                            loadedFiles.push(otherFiles[i].url);
                            $NL.log.i("文件加载完成：" + otherFiles[i].url);

                        }
                        //有文件加载完成，重新触发本函数进行轮循。
                        self.process()
                    });
                }
            }

        }),
        /**
        *模块定义
        */
        defined: function () {
            //参数重载判断
            var ns = arguments[0], ref, fun, nsObj;
            if (arguments.length == 2) {
                fun = arguments[1];
            } else if (arguments.length == 3) {
                ref = arguments[1];
                fun = arguments[2];
                if (typeof (ref) == "string") ref = [ref];
            }
            if (typeof (ns) != "string" || !$.isFunction(fun)) {
                $NL.log.e("模块定义的参数错误");
                return;
            }
//            if (Namespace.Exists(ns)){
//                $NL.log.i("模块对应的命名空间已加载, ns=" + ns);
//                $NL.script.namespaceLoaded(ns);
//                return;
//            }

            //使用script加载引用的脚本，如果ref为null或[],则马上执行function
            $NL.script.loadScripts(ref, function () {
                nsObj = Namespace.Register(ns);
                nsObj.className = ns;
                //将命名空间附加在函数上，以便在cls中通过caller取出
                fun.ns = nsObj;
                fun.apply(nsObj);
                $NL.script.namespaceLoaded(ns);
            }, ns);
        },
        /**
        *类定义，不使用class，防止在ie6、ie7上关键字错误
        *只有一个参数，并且为string时，根据类名返回类（失败时返回null)
        */
        cls: function () {
            //参数重载判断
            var ns = $NL.cls.caller.ns, className = "", base = null, fun = null, isStatic = false;
            if (arguments.length == 1) {
                if (typeof (arguments[0]) == "string") {
                    className = arguments[0];
                    if (Namespace.Exists(className)) {
                        var c = eval(className);
                        if ($.isPlainObject(c))
                            return c;
                        else if ($.isFunction(c))
                            return new c();
                    }
                    return null;
                }
                else {
                    fun = arguments[0];
                }
            }
            else if (arguments.length == 2) {
                if (typeof (arguments[0]) == "string")
                    className = arguments[0];
                else
                    base = arguments[0];

                fun = arguments[1];
            }
            else if (arguments.length == 3) {
                className = arguments[0];
                base = arguments[1];
                fun = arguments[2];
            }
            if (!$.isFunction(fun) || !$.isPlainObject(ns) || typeof (className) != "string") {
                $NL.log.e(["类定义中的参数错误:",className, ns, fun]);
                return;
            }
            
            //判断是否静态类
            if (className.indexOf("static") == 0) {
                if (className == "static") className = "";
                className = className.replace("static ", "");
                isStatic = true;
            }

            //对于类名为xxx.xxx.xxx的需要特殊处理
            if (className.indexOf(".") > 0) {
                var nsStr = ns.className + "." + className;
                var nsArr = nsStr.split(".");
                className = cnArr.pop();
                nsStr = cnArr.join(".");
                ns = Namespace.Register(nsStr);
                ns.className = nsStr;
            }

            if (isStatic && base != null && !$.isPlainObject(base)) {
                $NL.log.e("错误：静态类只能继承静态类,类名：" + ns.className + "." + className);
            }
            if (!isStatic && base != null && !$.isFunction(base)) {
                $NL.log.e("动态类只能继续动态类,类名：" + ns.className + "." + className);
            }
            
            var baseObj = base;
            if (!isStatic && base != null) {
                baseObj = new base();
            }
            
            var obj = ns;
            if (className == "") {
                //匿名类，方法附加在命名空间的对象中
                $.extend(obj, baseObj, { "base": baseObj, "className": ns.className, "isStatic": isStatic});
            }
            else {
                //声明类，附加在命名空间下以类名为名称的属性
                ns[className] = {};
                $.extend(ns[className], baseObj, { "base": baseObj, "className": ns.className + "." + className, "isStatic": isStatic});
                obj = ns[className];
            }
            //静态类为对象，动态类为函数
            if (isStatic) {
                fun.apply(obj);
                proxy(obj);
            }
            else {
                if (className == "") {
                    //ns = classProxy;//这样不行，只是改了引用，没改到实际的对象
                    eval(ns.className + "=classFactory");
                }
                else
                    ns[className] = classFactory;

            }
            
            //动态类创建工厂的函数，new时返回obj
            function classFactory() {
                $.extend(true, this, obj);
                fun.call(this);
                //取类名，如NengLong.XXX.AAA,取出AAA即为类名，再判断obj中有没有同名的属性函数，有则认为是构造函数，执行它。
                var l = this.className.split('.');
                var n = l[l.length - 1];
                if ($.isFunction(this[n])) {
                    try {
                        this[n].apply(this, arguments);
                    }
                    catch (err) {
                        $NL.log.e(this.className + "执行构造函数出错，错误信息：" + err.message);
                    }
                }
            }
            //对类中的所有函数的执行的上下文进行处理，使在调用过程中函数中的this指向类本身
            function proxy(o) {
                $.each(o, function (key, val) {
                    if ($.isFunction(val)) {
                        o[key] = $.proxy(val, o);
                    }
                });
            }
        }
    }

})(window);


